#include <cstdlib>
#include <iostream>
#include <sstream>

#include "application.h"
#include "paintCursor.h"
#include "colorBuffer.h"

#include <GL/glut.h>

using namespace std;

Application *application;
PaintCursor *cursor;
ColorBuffer *buffer;
bool drawb = false;

int winw = 814, winh = 814; // window size

GLfloat* nb = new GLfloat[winw * winh * 4];
GLfloat* pb = new GLfloat[winw * winh];

bool updateBuffers = false;

int buttonpressed;

/// time info
float cfps;
time_t start, end;
unsigned frame;

/// Info variables
void* fontsmall= GLUT_BITMAP_HELVETICA_12;
string fps("FPS: ");
string options("Zoom: + / -");

/// Renders info about the model
void renderInfo();
/// Returns the world coordinates of a point in screen space
void screenToWorld(int x, int y, double &xw, double &yw, double &zw);
/// Implements the "zoomin" operation. Narrows the view of the scene.
void zoomIn();
/// Implements the "zoomout" operation. Enlarges the view of the scene.
void zoomOut();

/// OpenGL display function
void display(void) {

   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

   glLoadIdentity();
   application->draw();

   glActiveTexture(GL_TEXTURE0);
   glDisable(GL_TEXTURE_2D);

   if (drawb) {
      GLuint t = buffer->texPositions;
      glEnable(GL_TEXTURE_2D);
      glBindTexture(GL_TEXTURE_2D, t);
      glBegin(GL_QUADS);
        glTexCoord2f(0.0f, 0.0f); glVertex3f(614.0f, 614.0f,  0.0f);
        glTexCoord2f(1.0f, 0.0f); glVertex3f(814.0f, 614.0f,  0.0f);
        glTexCoord2f(1.0f, 1.0f); glVertex3f(814.0f, 814.0f,  0.0f);
        glTexCoord2f(0.0f, 1.0f); glVertex3f(614.0f, 814.0f,  0.0f);
      glEnd();
      glDisable(GL_TEXTURE_2D);
      buffer->draw2();
   }

   cursor->update();
   if (!drawb) {
      buffer->paint();
      buffer->draw();
   }
   cursor->draw();

   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   glOrtho(-1, 1, -1, 1, -4, 4);
   glMatrixMode(GL_MODELVIEW);
   glLoadIdentity();

   renderInfo();

   glutSwapBuffers();
}

/// Mouse move function
void passivemousemove(int x, int y) {
   cursor->updatePosition(x, winh - y);
//   cout << x << ", " << winh - y << endl;
   glutPostRedisplay();
}

/// Mouse move function
void mousemove(int x, int y) {
   cursor->updatePosition(x, winh - y);
   if (buttonpressed == GLUT_LEFT_BUTTON) {
//      buffer->paint();
   } else if (buttonpressed == GLUT_MIDDLE_BUTTON) {
      application->mouseMiddleMotion(x, y);
   } else if ((buttonpressed == GLUT_RIGHT_BUTTON)) {
      application->mouseLeftMotion(x, y);
      //application->mouseRightMotion(x, y);
   }
   glutPostRedisplay();
}

/// Mouse press function
void mouseclick(int button, int state, int x, int y) {
//   cursor->updatePosition(x, winh - y);
   if (state == GLUT_DOWN) {
      buttonpressed = button;
      if (buttonpressed == GLUT_LEFT_BUTTON) {
         cursor->click();
//         buffer->paint();
      } else if (buttonpressed == GLUT_MIDDLE_BUTTON) {
         application->mouseMiddleButton(x, y);
      } else if ((buttonpressed == GLUT_RIGHT_BUTTON)) {
         application->mouseLeftButton(x, y);
         //application->mouseRightButton(x, y);
         updateBuffers = true;
      }
   } else if (state == GLUT_UP) {
      cursor->unclick();
      buffer->unpaint();
      application->mouseReleaseButton();
      if (updateBuffers) {
         updateBuffers = false;
         application->drawNormalBuffer(nb, winw, winh);
         application->drawPointIdsBuffer(pb, winw, winh);
         cursor->setNormalBuffer(nb);
         cursor->setCamera(application->getCamera());
      }
   }
   glutPostRedisplay();
}

/// OpenGL key press function
void keypress(unsigned char key, int x, int y) {

   switch (key) {
      case 27: {
         exit(1);
         break;
      }
      case '+': {
         cursor->in();
         break;
      }
      case '-': {
         cursor->out();
         break;
      }
      case '.': {
         cursor->increase();
         break;
      }
      case ',': {
         cursor->decrease();
         break;
      }
      case '0': {
         application->changeSelectedObjsMaterial(0);
         break;
      }
      case '1': {
         application->changeSelectedObjsMaterial(1);
         break;
      }
      case '2': {
         application->changeSelectedObjsMaterial(2);
         break;
      }
      case '3': {
         application->changeSelectedObjsMaterial(3);
         break;
      }
      case '4': {
         application->changeSelectedObjsMaterial(4);
         break;
      }
      case '5': {
         application->changeSelectedObjsMaterial(5);
         break;
      }
      case 'q': {
         cursor->setColor(Color(1.0, 0.0, 0.0, 1.0));
         break;
      }
      case 'w': {
         cursor->setColor(Color(0.0, 1.0, 0.0, 1.0));
         break;
      }
      case 'e': {
         cursor->setColor(Color(0.0, 0.0, 1.0, 1.0));
         break;
      }
      case 'r': {
         cursor->setColor(Color(1.0, 1.0, 0.0, 1.0));
         break;
      }
      case 't': {
         cursor->setColor(Color(1.0, 0.0, 1.0, 1.0));
         break;
      }
      case 'y': {
         cursor->setColor(Color(0.0, 1.0, 1.0, 1.0));
         break;
      }
      case 'o': {
         cursor->setColor(Color(1.0, 1.0, 1.0, 1.0));
         break;
      }
      case 'p': {
         cursor->setColor(Color(0.0, 0.0, 0.0, 1.0));
         break;
      }
      case 'z': {
         GLuint t = buffer->textureTest();
         application->changeRendererType(PYRAMID_POINTS_TEXTURE);
         application->setRenderTexture(t);
         drawb = !drawb;
         break;
      }
      case 'n': {
         for (int i = 0; i < winw * winh; i++)
            if (pb[i] != -1) cout << pb[i] << " ";
         break;
      }
   }
   glutPostRedisplay();
}

/// OpenGL reshape funcion
void reshape(int w, int h) {
   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   glOrtho(-1, 1, -1, 1, -4, 4);

   glMatrixMode(GL_MODELVIEW);
   glLoadIdentity();
   winw = w;
   winh = h;
   glViewport(0, 0, (GLsizei) w, (GLsizei) h);
   glutPostRedisplay();
}

/// Renders a string
void renderBitmapString(float x, float y, float z, void *font,
      const char *string) {
   const char *c;
   glRasterPos3f(x, y, z);
   for (c=string; *c != '\0'; c++) {
      glutBitmapCharacter(font, *c);
   }
}

/// Renders info about the animation
void renderInfo() {
   glLoadIdentity();
   glColor4f(0, 0, 0, 1);
   renderBitmapString(-0.92, 0.90, 0, (void *)fontsmall, fps.c_str());
   renderBitmapString(-0.92, 0.85, 0, (void *)fontsmall, options.c_str());
}

/// timer func
void timer(int time) {
   //void idle(){

   /// FPS info
   frame++;
   std::time(&end);
   double ddifftime = std::difftime(end, start);
   if (ddifftime > 1) {
      cfps = frame/ddifftime;
      start = end;
      frame = 0;
      ostringstream s1;
      s1<<cfps;
      fps = "FPS: " + s1.str();
   }

   glutPostRedisplay();
   glutTimerFunc(time, timer, time);
}

void showUsage(string progName) {
   cout << "Usage: " << progName << " file.ply\n";
}

/// OpenGL initializations
void init(void) {
   std::time(&start);
   frame = 0;

   glClearColor (0.0, 0.0, 0.0, 0.0);
   glShadeModel(GL_FLAT);
   glEnable(GL_DEPTH_TEST);
}

/// main function
int main(int argc, char **argv) {

   if (argc < 2) {
      showUsage(argv[0]);
      return (0);
   }

   // Glut setup
   glutInit(&argc, argv);
   glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH );
   glutInitWindowSize(winw, winh);
   glutInitWindowPosition(200, 50);
   glutCreateWindow("3D Texture Painting of Point Models");

   // OpenGL and data initialization
   init();

//   GLuint t;
//   vector<float> ti;
//   ti.push_back(0.0);
//   ti.push_back(0.0);
//   ti.push_back(1.0);
//   ti.push_back(0.0);
//
//   glEnable(GL_TEXTURE_2D);
//   glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
//   glGenTextures(1, &t);
//   glBindTexture(GL_TEXTURE_2D, t);
//   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
//   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
//   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
//   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
//   glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_FLOAT, &ti[0]);
//   glDisable(GL_TEXTURE_2D);

   application = new Application(PYRAMID_POINTS);
   application->readFile(argv[1]);
   application->setDepthTest(true);
   application->setPerVertexColor(false, 0);
   application->setSelectedObject(0);

//   application->setRenderTexture(t);
//   application->changeRendererType(PYRAMID_POINTS_TEXTURE);
//   application->changeSelectedObjsMaterial(5);

   application->drawNormalBuffer(nb, winw, winh);
   application->drawPointIdsBuffer(pb, winw, winh);

   cursor = new PaintCursor();
   cursor->setNormalBuffer(nb);
   cursor->setIdPointBuffer(pb);
   cursor->setBuffersSize(winw, winh);
   cursor->setCamera(application->getCamera());

   buffer = new ColorBuffer(cursor, winw, winh);
   buffer->setSurfels(application->getSurfelsList());

   // Callbacks registration
   glutDisplayFunc(display);
   glutReshapeFunc(reshape);
   glutMouseFunc(mouseclick);
   glutMotionFunc(mousemove);
   glutPassiveMotionFunc(passivemousemove);
   glutKeyboardFunc(keypress);
   glutTimerFunc(6, timer, 6);
   glutMainLoop();

   delete pb;
   delete nb;

   return 0;
}
